import xml.etree.ElementTree as ET
from Timestamp import Timestamp
import time

# DerbyXML is a parallel data structure to the underlying xml.  They are made lazily
# Similar to NSObject, we can add observers to the properties of a given node
# as well:
#       @attribute - when an attribute is set
#       children() - when the children are set
#       text() - the text of body of the element
#       .//node - when any node of this type is added/deleted or changed
class DerbyXML(object):
    def __init__(self, xml, parentmap=None):
        self._xml = xml
        if parentmap is None:  # parent map generated for root (because no "getparent" - ugh)
            self.parent_map = dict((c, p) for p in xml.getiterator() for c in p)
            for child in xml:
                self.parent_map[child] = xml
            print "Parent map", self.parent_map
        else:
            self.parent_map = parentmap
        xml._ftd = self  # put a circular link back to us
        self._observers = {}
        # self._myModDate = 0
        # self._latestModDate = 0
        if self.parent:
            self.parent.didChangeValue(".//" + self.elementName)

    def __getitem__(self, item):
        return self._xml.get(item)

    def __setitem__(self, item, value):
        if value is None:
            return self.__delitem__(item)
        self._xml.set(item, value)
        self.didChangeValue("@" + item, value)
        if self.parent:
            self.parent.didChangeValue(".//" + self.elementName)

    def __delitem__(self, item):
        if self._xml.get(item, None) is not None:
            del self._xml.attrib[item]
            self.didChangeValue("@" + item, None)
            if self.parent:
                self.parent.didChangeValue(".//" + self.elementName)

    # @property
    # def modDate(self):
    #     return self._myModDate
    #
    # @modDate.setter
    # def modDate(self, date):
    #     self._myModDate = date
    #     self.latestModDate = date
    #
    # @property
    # def latestModDate(self):
    #     return self._latestModDate
    #
    # @latestModDate.setter
    # def latestModDate(self, date):
    #     if date > self._latestModDate:
    #         self._latestModDate = date
    #         if self.parent is not None:
    #             self.parent.latestModDate = date


    @property
    def attributes(self):
        return self._xml.attrib

    @property
    def text(self):
        return self._xml.text

    @text.setter
    def text(self, text):
        self._xml.text = text
        self.didChangeValue("text()", text)

    @property
    def parent(self):
        return self.cachedOrCreate(self.parent_map.get(self._xml))

    @property
    def children(self):
        return [self.cachedOrCreate(x) for x in self._xml.getchildren()]

    @children.setter
    def children(self, children):
        self._xml.children = children
        self.didChangeValue("children()", self.children)

    def addChildXML(self, xml):
        self._xml.append(xml)
        self.parent_map[xml] = self._xml
        self.didChangeValue("children()", self.children)
        return self.cachedOrCreate(xml)

    @property
    def timestamp(self):
        if self._xml.get("timestamp") is None:
            return None
        return Timestamp(self._xml.get("timestamp"), self)

    def addObserver(self, observer, attrname):
        observers = self._observers.get(attrname)
        if observers is None:
            observers = []
            self._observers[attrname] = observers
        if not observer in observers:
            observers.append(observer)
            # and call back
            observer(self, attrname, self.__dict__.get(attrname, None))

    def removeObserver(self, observer):
        for attrname in self._observers:
            try:
                self._observers[attrname].remove(observer)
            except ValueError:
                pass

    def didChangeValue(self, attrname, value=None):
        #print "%%% didChangeValue", attrname, value
        if value is None:
            value = self.__dict__.get(attrname, None)
        for observer in self._observers.get(attrname, []):
            observer(self, attrname, value)

    @classmethod
    def classForTag(self, tag):
        # convert name-name to NameName
        clsName = "".join([x.title() for x in tag.split("-")])
        cls = globals()[clsName]
        return cls

    def cachedOrCreate(self, child):
        """Get the cached DerbyXML from the given xml, or create one"""
        if child is None:
            print "Can't create None child of", self, "/", self._xml
            return None
        if hasattr(child, "_ftd"):
            return child._ftd
        cls = DerbyXML.classForTag(child.tag)
        return cls(child, self.parent_map)

    def allNodesWithTag(self, tag):
        retval = self.findChildrenOfType(tag)
        for child in self.children:
            retval = retval + child.allNodesWithTag(tag)
        return retval

    def findChildrenOfType(self, tag, filter=None):
        retval = []
        for child in self._xml:
            if child.tag == tag:
                if filter:
                    if not filter(child):
                        continue
                retval.append(self.cachedOrCreate(child))
        return retval

    def findChildOfTypeById(self, tag, id, attr='id'):
        def filter(child):
            return child.get(attr) == id

        retval = self.findChildrenOfType(tag, filter)
        if retval:
            return retval[0]
        return None

    def findChildrenOfTypes(self, tags, filter=None):
        retval = []
        for child in self._xml:
            if (child.tag in tags) and (filter is None or filter(child)):
                retval.append(self.cachedOrCreate(child))
        return retval

    def find(self, xpath):
        found = self._xml.find(xpath)
        if found is not None:
            return self.cachedOrCreate(found)
        return None

    @property
    def isActive(self):
        """ Returns if a node has a start timestamp, but no stop timestamp """
        if self._xml.get("timestamp"):
            if self._xml.get("duration"):
                return False
            return True
        return False

    @property
    def elementName(self):
        return self._xml.tag

    def Enclosing(self, tag):
        j = self._xml
        while j is not None:
            if j.tag == tag:
                return self.cachedOrCreate(j)
            j = self.parent_map.get(j)
        return None

    def EnclosingJam(self):
        return self.Enclosing('jam')

    def EnclosingPeriod(self):
        return self.Enclosing('period')

    def EnclosingBout(self):
        return self.Enclosing('bout')

    def EnclosingTeam(self):
        return self.Enclosing('team')

    def bouts(self):
        return self.findChildrenOfType('bout')

    def teams(self):
        return self.findChildrenOfType('team')

    def venues(self):
        return self.findChildrenOfType('venue')

    def persons(self):
        return self.findChildrenOfType('person')

    def notes(self):
        return self.findChildrenOfType('note')

    @classmethod
    def fromString(cls, string):
        xml = ET.fromstring(string)
        ctor = DerbyXML.classForTag(xml.tag)
        return ctor(xml)

    @classmethod
    def parse(cls, string):
        xml = ET.parse(string)
        ctor = DerbyXML.classForTag(xml.tag)
        return ctor(xml)

    @classmethod
    def defaultBout(cls, rules=None):
        xml = ET.Element("bout", {})
        xml.append(ET.Element("venue"))
        if rules:
            xml.append(rules)
        else:
            xml.append(ET.Element("rules"))
        home = ET.Element("team", {'id': 'home'})
        home.append(ET.Element("person", {'number': "???", 'name': "Unknown", 'id': "Home_???"}))
        away = ET.Element("team", {'id': 'away'})
        away.append(ET.Element("person", {'number': "???", 'name': "Unknown", 'id': "Away_???"}))
        xml.append(home)  # home
        xml.append(away)  # away
        xml.append(ET.Element("team"))  # officials
        xml.append(ET.Element("period", {'number': '1'}))  # first period
        xml.append(ET.Element("period", {'number': '2'}))  # second period
        # create timers (actually, get default values from rules if possible)
        xml.append(ET.Element("timer", {'id': 'period', 'duration': '1800', 'counts-down': 'yes'}))
        xml.append(ET.Element("timer", {'id': 'jam', 'duration': '120', 'counts-down': 'yes'}))
        xml.append(ET.Element("timer", {'id': 'lineup', 'duration': '3600'}))
        xml.append(ET.Element("timer", {'id': 'timeout', 'duration': '3600'}))
        xml.append(ET.Element("timer", {'id': 'intermission', 'duration': '900', 'counts-down': 'yes'}))  # 15 minutes
        retval = Bout(xml)
        retval.findChildrenOfType("timer")  # create the DerbyXML versions for binding
        return retval

    def tostring(self):
        return ET.tostring(self._xml)

    def UpdateBoutState(self, state):
        """ Support for updating a bout state based on the current event """
        return state

    def takeAttributesAndChildrenFrom(self, xml):
        self._xml.clear()
        for pair in xml.items():
            self[pair[0]] = pair[1]
        for child in xml:
            self._xml.append(child)
        self.didChangeValue("children()")
        self.didChangeValue("text()")

    @property
    def name(self):
        return self['name']

    @name.setter
    def name(self, value):
        self['name'] = value
        self.didChangeValue("name", value)

    @property
    def id(self):
        return self['id']

    @id.setter
    def id(self, value):
        self['id'] = value
        self.didChangeValue("id", value)

    @property
    def bout(self):
        return self.EnclosingBout()

    @property
    def team(self):
        teamname = self['team']
        if teamname:
            for team in self.bout.teams():
                if team.name == teamname:
                    return team
        return None

    @team.setter
    def team(self, teamxml):
        self['team'] = teamxml.name
        self.didChangeValue("team", teamxml)

    @property
    def skater(self):
        skaterid = self['skater']
        skatername = self.name
        teamname = self['team']
        #print "finding skater", skaterid, skatername, teamname
        if skaterid:
            for team in self.bout.teams():
                #print "looking in", team.tostring()
                for person in team.persons():
                    #print "checking person", person.tostring()
                    if person.id == skaterid:
                        return person
                    if person.name == skatername and teamname == team.name:
                        return person

    @skater.setter
    def skater(self, skaterxml):
        if skaterxml.id:
            self['skater'] = skaterxml.id
        teamxml = skaterxml.EnclosingTeam()
        if teamxml:
            self['team'] = teamxml.name
            self['name'] = skaterxml.name
        self.didChangeValue("skater", skaterxml)

    @property
    def periodNumber(self):
        return self.EnclosingPeriod().periodNumber

    @property
    def jamNumber(self, ):
        return self.EnclosingJam().jamNumber


class Note(DerbyXML):
    pass


class Bout(DerbyXML):
    def periods(self):
        return self.findChildrenOfType('period')

    def teams(self):
        return self.findChildrenOfType('team')

    def dumpTimes(self):
        pnum = 1
        for p in self.periods():
            jnum = 1
            for j in p.children:
                pj = "P%d J%-2d|" % (pnum, jnum)
                ts = j.timestamp
                if ts is not None:
                    print pj, ts.HHMMSSStr(), ts.PMMSSStr(), j.elementName
                for e in j.children:
                    ts = e.timestamp
                    if ts is not None:
                        print pj, ts.HHMMSSStr(), ts.PMMSSStr(), "\t", e.elementName
                if j.elementName == "jam":
                    jnum += 1
            pnum += 1

    @property
    def home(self):
        teams = self.findChildrenOfType('team')
        if len(teams) > 0:
            return teams[0]
        return None

    @home.setter
    def home(self, value):
        team = self.home
        if team:
            team.takeAttributesAndChildrenFrom(value)

    @property
    def away(self):
        teams = self.findChildrenOfType('team')
        if len(teams) > 1:
            return teams[1]
        return None

    @away.setter
    def away(self, value):
        team = self.away
        if team:
            team.takeAttributesAndChildrenFrom(value)

    @property
    def officials(self):
        teams = self.findChildrenOfType('team')
        if len(teams) > 2:
            return teams[2]
        return None

    @officials.setter
    def officials(self, value):
        team = self.officals
        if team:
            team.takeAttributesAndChildrenFrom(value)

    def UpdateBoutState(self, state):
        """ Update based on periods """
        import BoutStates

        for period in self.periods():
            state = period.UpdateBoutState(state)
            if state not in (BoutStates.PREBOUT, BoutStates.INTERMISSION, BoutStates.PENDING):
                return state
        return state

    @property
    def parent(self):
        return None  # there is no parent of the root bout configuration


class Team(DerbyXML):
    @property
    def skaters(self):
        def filter(child):
            return child.get('number') is not None

        return self.findChildrenOfType('person', filter)


class Venue(DerbyXML):
    pass


class Person(DerbyXML):
    @property
    def number(self):
        return self['number']


class Period(DerbyXML):
    def jams(self):
        return self.findChildrenOfType('jam')

    def timeOuts(self):
        return self.findChildrenOfType('time-out')

    def UpdateBoutState(self, state):
        import BoutStates

        ts = self.timestamp
        state = BoutStates.PENDING
        if ts is not None:
            if time.time() < ts.Absolute():  # haven't started yet
                if self.parent.periods[0] == self:  # if the first period hasn't start, prebout
                    return BoutStates.PREBOUT
                else:
                    return BoutStates.INTERMISSION  # otherwise intermission
            else:
                for event in self.children:
                    state = event.UpdateBoutState(state)
        return state

    @property
    def periodNumber(self):
        return self.parent.periods().index(self) + 1


class Jam(DerbyXML):
    def events(self):
        return self.findChildrenOfTypes(["line-up", "pack-lap",
                                         "penalty", "pass", "star-pass", "action",
                                         "error", "lead", "lost-lead", "call",
                                         "enter-box", "exit-box", "box-time", "leave-track",
                                         "return-track", "injury"])

    def eventsForTeam(self, team):
        return self.findChildrenOfTypes(["line-up", "pack-lap",
                                         "penalty", "pass", "star-pass", "action",
                                         "error", "lead", "lost-lead", "call",
                                         "enter-box", "exit-box", "box-time", "leave-track",
                                         "return-track", "injury"], filter=lambda xml: xml.get('team') == team.id)

    def lineupForTeam(self, team):
        retval = {'blockers': [], 'pivot': None, 'jammer': None}
        for e in self.findChildrenOfType("line-up"):
            if e['team'] == team.id:
                #print "lineup",e.tostring()
                pos = e['position']
                if pos == 'blocker':
                    retval['blockers'].append(e.skater)
                else:
                    retval[pos] = e.skater
        return retval

    def passes(self):
        return self.findChildrenOfType("pass")

    def UpdateBoutState(self, state):
        import BoutStates

        ts = self.timestamp
        if ts is not None:
            if self['duration'] is None:
                return BoutStates.JAM_IS_ON
            else:
                return BoutStates.LINEUP
        return state

    @property
    def jamNumber(self):
        return self.EnclosingPeriod().jams().index(self) + 1


class Timer(DerbyXML):
    pass


class Rules(DerbyXML):
    pass


class LineUp(DerbyXML):
    pass


class PackLap(DerbyXML):
    pass


class Penalty(DerbyXML):
    pass


class Pass(DerbyXML):
    pass


class StarPass(DerbyXML):
    pass


class Action(DerbyXML):
    pass


class Error(DerbyXML):
    pass


class Lead(DerbyXML):
    pass


class LostLead(DerbyXML):
    pass


class Call(DerbyXML):
    pass


class EnterBox(DerbyXML):
    pass


class ExitBox(DerbyXML):
    pass


class BoxTime(DerbyXML):
    pass


class LeaveTrack(DerbyXML):
    pass


class ReturnTrack(DerbyXML):
    pass


class Injury(DerbyXML):
    def UpdateBoutState(self, state):
        import BoutStates

        return BoutStates.INJURY


class TimeOut(DerbyXML):
    def UpdateBoutState(self, state):
        import BoutStates

        team = self['team']
        if team is not None:
            return BoutStates.TEAM_TIMEOUT
        return BoutStates.OFFICIAL_TIMEOUT


class OfficialReview(DerbyXML):
    def UpdateBoutState(self, state):
        import BoutStates

        return BoutStates.OFFICIAL_REVIEW_TIMEOUT